﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web.Security;
using NTQOnlineExam.Services.CommandService.Account;
using NTQOnlineExam.Services.CommandService.Baskets;
using NTQOnlineExam.Services.CommandService.Orders;
using NTQOnlineExam.Services.CommandService.Payment;
using NTQOnlineExam.Services.DBCommand.Account;
using NTQOnlineExam.Services.EntityFW.Entity;
using NTQOnlineExam.Services.EntityFW.Repository;
using NTQOnlineExam.Services.Infrastructure.Utility;
using NTQOnlineExam.Services.Models;
using NTQOnlineExam.Services.ReportService.Account;
using NTQOnlineExam.Services.ReportService.Basket;
using NTQOnlineExam.Services.ReportService.Portfolio;

namespace NTQOnlineExam.Services.CommandService
{
    public class BasketCommandService : IBasketCommandService
    {
        private readonly IGenericRepository<EntityFW.Entity.Term> _termRepository;
        private readonly IGenericRepository<EntityFW.Entity.Basket> _basketRepository;
        private readonly IGenericRepository<EntityFW.Entity.BasketItem> _basketItemRepository;
        private readonly IUnitOfWork _unitOfWork;
        private readonly IOrderCommandService _orderCommandService;
        private readonly IOrderPaymentInfoFactory _paymentOrderInfoFactory;
        private readonly IAccountCommandService _accountCommandService;
        private readonly IPaymentCommandService _paymentCommandService;
        private readonly IAccountReportService _accountReportService;
        private readonly IPortfolioReportService _portfolioReportService;
        private readonly IBasketReportService _basketReportService;

        public BasketCommandService(IGenericRepository<Term> termRepository, IGenericRepository<Basket> basketRepository, IUnitOfWork unitOfWork, IGenericRepository<BasketItem> basketItemRepository, IOrderCommandService orderCommandService, IOrderPaymentInfoFactory paymentOrderInfoFactory, IPaymentCommandService paymentCommandService, IAccountCommandService accountCommandService, IAccountReportService accountReportService, IPortfolioReportService portfolioReportService, IBasketReportService basketReportService)
        {
            _termRepository = termRepository;
            _basketRepository = basketRepository;
            _unitOfWork = unitOfWork;
            _basketItemRepository = basketItemRepository;
            _orderCommandService = orderCommandService;
            _paymentOrderInfoFactory = paymentOrderInfoFactory;
            _paymentCommandService = paymentCommandService;
            _accountCommandService = accountCommandService;
            _accountReportService = accountReportService;
            _portfolioReportService = portfolioReportService;
            _basketReportService = basketReportService;
        }

        public bool AddTerm(int termId, string comment, out string message)
        {
            Term term;
            if (!ValidateTerm(termId, out term, out message)) return false;
            return AddTerm(term, comment);
        }


        public Basket GetBasketById(Guid basketId)
        {
            return _basketRepository.GetByID(basketId);
        }

        public Basket GetCurrentBasket()
        {
            MembershipUser session = _accountReportService.GetCurrentUser();
            var basket = _basketRepository.Get(b => string.Compare(b.UserName, session.UserName, StringComparison.OrdinalIgnoreCase) == 0).FirstOrDefault();
            if (basket == null)
            {
                basket = new Basket
                {
                    BasketId = Guid.NewGuid(),
                    UserName = session.UserName,
                    CreationDate = DateTime.Now
                };

                _unitOfWork.Repository<Basket>().Insert(basket);
                _unitOfWork.Save();
            }
            return basket;
        }

        public bool RemoveBasketItem(Guid itemId)
        {
            Guard.Against<ApplicationException>(itemId.Equals(Guid.Empty), "Item Id is required");
            var basketItem = _basketItemRepository.GetByID(itemId);
            Guard.Against<ApplicationException>(basketItem == null, string.Format("Cannot find basket item having Id ={0} in your current shopping basket.", itemId));
            _unitOfWork.Repository<BasketItem>().Delete(basketItem);
            _unitOfWork.Save();
            return true;
        }

        public bool Checkout(CheckoutCommand command, out string message)
        {
            var basket = _basketReportService.GetCurrentBasket();
            Guard.Against<ApplicationException>(basket==null,  "Can not find shopping basket that belongs to current user");
            Guard.Against<ApplicationException>(basket.BasketItems == null||basket.BasketItems.Count<=0, "Your shopping basket is empty, cannot checkout.");
            OrderDTO order;
            try
            {
                _unitOfWork.BeginTransaction();
                if (_accountReportService.GetCurrentUser() != null)
                {

                    var updateAddressInfo = new UpdateUserAddressCommand()
                    {
                        UserId = (int)_accountReportService.GetCurrentUser().ProviderUserKey,
                        BillingAddress = command.BillingAddress,
                        BillingCity = command.BillingCity,
                        BillingZipCode = command.BillingZipCode,
                        BillingCountry = command.BillingCountry,
                        BillingPOBox = command.BillingPOBox,
                        FirstName = command.FirstName,
                        LastName = command.LastName
                    };
                    _accountCommandService.UpdateUserAddressInformation(updateAddressInfo);
                }
                
                var createOrderCommand = new CreateNewOrderCommand()
                {
                    BillingAddress = command.BillingAddress,
                    BillingCity = command.BillingCity,
                    BillingZipCode = command.BillingZipCode,
                    BillingCountry = command.BillingCountry,
                    Email = command.Email,
                    FirstName = command.FirstName,
                    LastName = command.LastName
                };
                order = _orderCommandService.CreateNewOrder(createOrderCommand);

                _unitOfWork.Save();
                _unitOfWork.Commit();
            }
            catch (Exception ex)
            {
                _unitOfWork.Rollback();
                throw new Exception(ex.Message, ex);
            }

            var paymentOrderDto = _paymentOrderInfoFactory.Create(order);
            var paymentResult = _paymentCommandService.RequestPayment(paymentOrderDto, out message);
            return paymentResult;
        }

        #region Helpers

        private bool ValidateTerm(int termId, out Term term, out string message)
        {
            message = "";
            term = _termRepository.GetByID(termId);
            if (term == null)
            {
                message = string.Format("Can not find term having Id = {0}", termId);
                return false;
            }

            //Check portfolio
            if (!_portfolioReportService.CheckTermNotInPortfolio(termId))
            {
                message = string.Format("You already have this term in your portfolio");
                return false;
            }
            return true;
        }

        private bool AddTerm(Term term, string comment)
        {
            Guard.Against<ApplicationException>(term == null, "Term can not be null");
            Guard.Against<ApplicationException>(!term.Price.HasValue || term.Price.Value <= 0, "Price can not be less than or equal to zero");
            var basket = GetCurrentBasket();

            var termExisted = _basketItemRepository.Get(i => i.BasketId.Equals(basket.BasketId) && i.TermId == term.TermId).Any();
            Guard.Against<ApplicationException>(termExisted, "The selected term already added.");

            var basketItem = new BasketItem
            {
                Id = Guid.NewGuid(),
                BasketId = basket.BasketId,
                TermName = term.TermName,
                TermId = term.TermId,
                Comment = comment,
                Price = term.Price.HasValue ? term.Price.Value : 0
            };

            _unitOfWork.Repository<BasketItem>().Insert(basketItem);
            _unitOfWork.Save();
            return true;
        }

        #endregion
    }
}
